你有沒有很常看過具有連續說故事感的網頁,例如從咖啡豆變成咖啡、從雞蛋和麵粉變成鬆餅(板塊設計)。
我曾經也超級好奇,那些物件是怎麼如此絲滑倒車入庫,放入最適當的位置,貫穿全場的?
適用 GSAP Timeline + ScrollTrigger,讓畫面中的物件隨著滾動一步步變化位置、縮放,甚至翻轉。讓網頁不只是單純上下滑,而是帶有動畫過渡的「故事感」。
首先,我們需要一個 主要容器 section
,裡面放多個「場景」。
另外,在畫面中央放主要要移動的物件,並透過 absolute
定位在畫面。
<section ref={mainRef} className="relative scroll-container z-10 text-6xl font-bold">
<div id="section1" className="scroll-section h-screen bg-amber-50 flex items-center justify-center">
<h2>Introduction</h2>
</div>
<div id="section2" className="scroll-section h-screen bg-blue-200 flex items-center justify-center">
<h2>My Skills</h2>
</div>
<div id="section3" className="scroll-section h-screen bg-violet-200 flex items-center justify-center">
<h2>Projects</h2>
</div>
<div id="section4" className="scroll-section h-screen bg-red-200 flex items-center justify-center">
<h2>Contact</h2>
</div>
</section>
主要要移動的物件獨立放外層:
<img
ref={boxRef}
src=""
alt=""
className="w-80 z-20 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
style={{ transformStyle: 'preserve-3d' }} // 讓 3D 翻轉可見
/>
使用 useGSAP
Hook 來管理動畫。GSAP Timeline 好處是:
useGSAP(() => {
const box = boxRef.current;
const tl = gsap.timeline({
scrollTrigger: {
trigger: mainRef.current, // 綁定大容器
pin: box, // 固定角色圖片
scrub: 1, // 平滑跟隨捲動
start: "top top", // 從容器頂部開始
end: "bottom bottom", // 到容器底部結束
}
});
mainRef
,也就是整個大區塊。box
(角色圖片)釘住,不會跟著頁面滑掉。1
表示滾動與動畫同步,並且加上一點平滑過渡。希望每個 Section 對應角色的一個狀態。
GSAP Timeline 的特點是:不同 to
會被均勻分配到滾動範圍。
這裡 x、y 要移動多少就需要慢慢試或靠經驗,建議都是用vh vw,RWD才好控制。
// Section 1 (初始狀態)
tl.to(box, { duration: 1, ease: "none" });
// Section 2 (角色往右下 & 縮小)
tl.to(box, {
x: "25vw",
y: "10vh",
scale: 0.8,
duration: 1,
ease: "none"
});
// Section 3 (角色往左上 & 放大 & 翻轉)
tl.to(box, {
x: "-20vw",
y: "-10vh",
scale: 1.2,
rotationY: 180, // 翻轉 180 度
duration: 1,
ease: "none"
});
// Section 4 (回到中央 & 縮小)
tl.to(box, {
x: "0vw",
y: "0vh",
scale: 0.5,
rotationY: 360, // 轉回正面
duration: 1,
ease: "none"
});
// 結尾加點留白
tl.to(box, { duration: 0.5, ease: "none" });
}, { scope: mainRef });
好的完成啦!(ง •̀_•́)ง
以下程式碼可以直接貼上測試(需要安裝 gsap
與 @gsap/react
):
import Header from '../component/header.jsx';
import React, { useRef } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useGSAP } from '@gsap/react';
gsap.registerPlugin(ScrollTrigger);
const Scroll = () => {
const mainRef = useRef(null);
const boxRef = useRef(null);
useGSAP(() => {
const box = boxRef.current;
const tl = gsap.timeline({
scrollTrigger: {
trigger: mainRef.current,
pin: box,
scrub: 1,
start: 'top top',
end: 'bottom bottom',
}
});
tl.to(box, { duration: 1, ease: 'none' });
tl.to(box, { x: '25vw', y: '10vh', scale: 0.8, duration: 1, ease: 'none' });
tl.to(box, { x: '-20vw', y: '-10vh', scale: 1.2, rotationY: 180, duration: 1, ease: 'none' });
tl.to(box, { scale: 0.5, rotationY: 360, duration: 1, ease: 'none' });
tl.to(box, { duration: 0.5, ease: 'none' });
}, { scope: mainRef });
return (
<>
<Header className="mb-16" />
<img
ref={boxRef}
src="你的圖"
alt=""
className="w-80 z-20 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
style={{ transformStyle: 'preserve-3d' }}
/>
<section ref={mainRef} className="relative scroll-container z-10 text-6xl font-bold">
<div id="section1" className="scroll-section h-screen flex items-center justify-center bg-amber-50">
<h2>Introduction</h2>
</div>
<div id="section2" className="scroll-section h-screen flex items-center justify-center bg-blue-200">
<h2>My Skills</h2>
</div>
<div id="section3" className="scroll-section h-screen flex items-center justify-center bg-violet-200">
<h2>Projects</h2>
</div>
<div id="section4" className="scroll-section h-screen flex items-center justify-center bg-red-200">
<h2>Contact</h2>
</div>
</section>
</>
);
};
export default Scroll;